#pragma once
#include "Message.h"
#include <boost/asio.hpp>
#include <map>
#include "ThreadSafeQueue.h"
#include "Object.h"

#define MAX_SIZE 8192
#define SEND_PERIOD 50


// placeholder for a message
class Message: public MessageHeader 
{
public:
	Message(): MessageHeader(0,0,0){}
	unsigned char body[1024];
};

enum networkErrorCode { SEND_ERROR, RECEIVE_ERROR };


// exception class
class NetworkException
{
public:
	NetworkException(networkErrorCode errorCode)
	{
		switch(errorCode){
			case SEND_ERROR: 
				errorMessage = "Send failed";
				break;
			case RECEIVE_ERROR: 
				errorMessage = "Receive failed";
				break;
		}
	}

	std::string errorMessage;
};

class Network 
{
	
public:
	Network();
	~Network();

	/// <summary>
	///	Should be called before any other method. Initializes the necessary attributes for the connection.
	///	</summary> 
	///	<param name="listening">
	///	If <c>true</c> starts a new UDP-socket for sending and the send-thread becomes bound to the <c>sendThreadMain</c>-method.
	///	If <c>false</c> initializes the sending socket by the receive socket and a random port.
	///	</param>
	static void init(bool listening = true);

	///	<summary>
	///	Binds the <c>receiveMainThread</c>-method to the receive-thread.
	///	</summary>
	static void startReceiving();
	static void stopReceiving();

	///	<summary>
	///	Appends a message to the message-queue of all receivers of the receiver-queue.
	///	FIXME: sendMessage should overwrite messaged with the same id which are already queued.
	/// </summary>
	///	<param name="message">
	///	A message of this header becomes appended.
	/// </param>
	static void sendMessage(MessageHeader *message);

	///	<summary>
	///	In difference to the <c>sendMessage</c>-method this one tries to send the message at the time the method is called.
	///	</summary>
	/// <param name="message">
	///	<see cref="Network::sendMessage"></see>
	///	</param>
	static void sendMessageNow(MessageHeader *message);

	///	<summary>
	///	Calls the <c>handleMessage</c>-method of <c>Object</c> for every endpoint in <c>_receiveBuffer</c>.
	///	</summary>
	static void receiveMessage();
	
	///	<summary>
	///	Attaches a new receiver with a message-queue to <c>_receiverQueue</c>.
	///	</summary>
	///	<param name="endpoint">
	///	Stores the new receiver by the passed endpoint.
	///	</param>
	static void addReceiverEndpoint(boost::asio::ip::udp::endpoint endpoint);

	///	<summary>
	///	Erases a receiver from <c>_receiverQueue</c> by the passed endpoint if it exists.
	///	<summary>
	static void removeReceiverEndpoint(boost::asio::ip::udp::endpoint endpoint);

	///	<summary>
	///	Uses the <c>boost::resolver</c> to return the correct endpoint by the passed host-name.
	///	</summary>
	static boost::asio::ip::udp::endpoint getEndpoint( const char* hostName );
	static boost::asio::ip::udp::endpoint getLocalEndpoint();

private:
	///	<summary>
	///	Sends all stored messages of the message-queue to the appropriate every receiver. The sending is done every few milliseconds depending on the count of receivers.
	/// </summary>
	static void sendThreadMain();

	///	<summary>
	///	Calls <c>receivePacket</c> if receiving has started.
	///	</summary>
	static void receiveThreadMain();
	///	<summary>
	///	Casts received bytes into a <c>MessageHeader</c> and calls <c>onReceiveMessage</c> of <c>Object</c> passing this header as argument.
	///	</summary>
	static void receivePacket();
	
	static bool			 _isReceiving;
	static boost::thread _sendThread;
	static boost::thread _receiveThread;

	static std::map<ID, boost::asio::ip::udp::endpoint>	_receiveBuffer;

	static boost::mutex _networkMutex;
	static boost::mutex _receiveBufferMutex;
	static std::map<boost::asio::ip::udp::endpoint, ThreadSafeQueue<MessageHeader*> *> _receiverQueue;
	static boost::asio::io_service *io_service;
	static boost::asio::ip::udp::socket *sendSocket;
	static boost::asio::ip::udp::socket *receiveSocket;
	static boost::asio::ip::udp::endpoint localEndpoint;
};